EDA — Vehicles US¶

Autor: Sandra Quinones Proyecto: Panel de análisis de anuncios de venta de vehículos

Objetivos¶

  1. Explorar la estructura del dataset vehicles_us.csv.
  2. Visualizar distribuciones clave (kilometraje, precio).
  3. Explorar la relación precio vs. kilometraje mediante scatter interactivo.
  4. Preparar visualizaciones y componentes que se reutilizan en la app Streamlit.
In [1]:
# Librerías y configuración
import pandas as pd
import numpy as np
import plotly.express as px

# Opciones (para mostrar más columnas)
pd.set_option('display.max_columns', 50)
px.defaults.template = "plotly_white"
In [2]:
# Cargar dataset (archivo en la raíz del proyecto)
car_data = pd.read_csv('vehicles_us.csv')

# Arreglo de nombres de columnas: homogeneizar (minúsculas y quitar espacios)
car_data.columns = [c.strip().lower().replace(' ', '_') for c in car_data.columns]

# Vista rápida
print("Registros:", car_data.shape[0])
display(car_data.head(20))
display(car_data.info(20))
Registros: 51525
price model_year model condition cylinders fuel odometer transmission type paint_color is_4wd date_posted days_listed
0 9400 2011.0 bmw x5 good 6.0 gas 145000.0 automatic SUV NaN 1.0 2018-06-23 19
1 25500 NaN ford f-150 good 6.0 gas 88705.0 automatic pickup white 1.0 2018-10-19 50
2 5500 2013.0 hyundai sonata like new 4.0 gas 110000.0 automatic sedan red NaN 2019-02-07 79
3 1500 2003.0 ford f-150 fair 8.0 gas NaN automatic pickup NaN NaN 2019-03-22 9
4 14900 2017.0 chrysler 200 excellent 4.0 gas 80903.0 automatic sedan black NaN 2019-04-02 28
5 14990 2014.0 chrysler 300 excellent 6.0 gas 57954.0 automatic sedan black 1.0 2018-06-20 15
6 12990 2015.0 toyota camry excellent 4.0 gas 79212.0 automatic sedan white NaN 2018-12-27 73
7 15990 2013.0 honda pilot excellent 6.0 gas 109473.0 automatic SUV black 1.0 2019-01-07 68
8 11500 2012.0 kia sorento excellent 4.0 gas 104174.0 automatic SUV NaN 1.0 2018-07-16 19
9 9200 2008.0 honda pilot excellent NaN gas 147191.0 automatic SUV blue 1.0 2019-02-15 17
10 19500 2011.0 chevrolet silverado 1500 excellent 8.0 gas 128413.0 automatic pickup black 1.0 2018-09-17 38
11 8990 2012.0 honda accord excellent 4.0 gas 111142.0 automatic sedan grey NaN 2019-03-28 29
12 18990 2012.0 ram 1500 excellent 8.0 gas 140742.0 automatic pickup NaN 1.0 2019-04-02 37
13 16500 2018.0 hyundai sonata excellent 4.0 gas 22104.0 automatic sedan silver NaN 2019-01-14 29
14 12990 2009.0 gmc yukon excellent 8.0 gas 132285.0 automatic SUV black 1.0 2019-01-31 24
15 17990 2013.0 ram 1500 excellent 8.0 gas NaN automatic pickup red 1.0 2018-05-15 111
16 14990 2010.0 ram 1500 excellent 8.0 gas 130725.0 automatic pickup red 1.0 2018-12-30 13
17 13990 2014.0 jeep cherokee excellent 6.0 gas 100669.0 automatic SUV red 1.0 2018-08-16 25
18 12500 2013.0 chevrolet traverse excellent 6.0 gas 128325.0 automatic SUV white 1.0 2019-04-09 13
19 13990 2018.0 hyundai elantra excellent 4.0 gas 31932.0 automatic sedan red NaN 2018-08-25 27
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 51525 entries, 0 to 51524
Data columns (total 13 columns):
 #   Column        Non-Null Count  Dtype  
---  ------        --------------  -----  
 0   price         51525 non-null  int64  
 1   model_year    47906 non-null  float64
 2   model         51525 non-null  object 
 3   condition     51525 non-null  object 
 4   cylinders     46265 non-null  float64
 5   fuel          51525 non-null  object 
 6   odometer      43633 non-null  float64
 7   transmission  51525 non-null  object 
 8   type          51525 non-null  object 
 9   paint_color   42258 non-null  object 
 10  is_4wd        25572 non-null  float64
 11  date_posted   51525 non-null  object 
 12  days_listed   51525 non-null  int64  
dtypes: float64(4), int64(2), object(7)
memory usage: 5.1+ MB
None

Observaciones iniciales¶

  • Normalizamos nombres de columnas a minúsculas (por consistencia).
  • A continuación se limpian valores nulos en variables clave (precio, odometer, model_year) y se crea una columna auxiliar year_bucket para visualizaciones agrupadas por rango de año.
In [3]:
# Limpieza mínima necesaria para visualizaciones
# Convertir a numérico donde corresponde 
for col in ['price', 'odometer', 'model_year']:
    if col in car_data.columns:
        car_data[col] = pd.to_numeric(car_data[col], errors='coerce')

# Eliminar filas sin price o odometer (no son útiles para los gráficos)
car_data = car_data.dropna(subset=['price', 'odometer'])

# Rellenar model_year nulos con la mediana (si se necesita) o mantener para filtrar
# Aquí optamos por dejar nulos y no usarlos en gráficos que requieran model_year, o completar que luego si queremos con car_data['model_year'].fillna(...)
# Para tener columnas más legibles:
car_data['model_year'] = car_data['model_year'].astype('Int64')  # permite NA

# Crear columnas auxiliares: bucket de año (decadas) para color en histogramas
bins = [1900, 1990, 2000, 2010, 2015, 2018, 2020, 2022, 2025]
labels = ['<1990','1990s','2000s','2010-14','2015-17','2018-19','2020-21','2022+']
if 'model_year' in car_data.columns:
    car_data['year_bucket'] = pd.cut(car_data['model_year'].astype(float), bins=bins, labels=labels, include_lowest=True)
else:
    car_data['year_bucket'] = np.nan

# Muestra de confirmación
display(car_data[['price','odometer','model_year','year_bucket']].head())
price odometer model_year year_bucket
0 9400 145000.0 2011 2010-14
1 25500 88705.0 <NA> NaN
2 5500 110000.0 2013 2010-14
4 14900 80903.0 2017 2015-17
5 14990 57954.0 2014 2010-14

Nota de limpieza:

  • Convertimos price, odometer y model_year a numérico con errors='coerce' para que valores inválidos se marquen NA y puedan limpiarse con seguridad.
  • Decidimos no eliminar todas las filas con model_year faltante, porque perderíamos observaciones válidas para otros análisis; en cambio, solo filtramos cuando la gráfica lo requiere.
In [4]:
# Histograma por decade (year_bucket)
if car_data['year_bucket'].notna().sum() > 0:
    fig = px.histogram(
        car_data,
        x='odometer',
        color='year_bucket',
        nbins=50,
        title='Distribución del kilometraje por año de fabricación (bucket)',
        labels={'odometer':'Kilometraje', 'count':'Cantidad'},
        category_orders={'year_bucket': ['<1990','1990s','2000s','2010-14','2015-17','2018-19','2020-21','2022+']}
    )
    fig.update_layout(barmode='stack', legend_title='Año (bucket)')
else:
    # fallback simple si no hay year_bucket usable
    fig = px.histogram(car_data, x='odometer', nbins=50, title='Distribución del kilometraje')

fig.update_layout(title_x=0.5)
fig.show()

Observaciones sobre el histograma¶

  • El histograma está coloreado por intervalos de model_year (decadas).
  • Puedes hacer clic en un elemento de la leyenda para aislar ese grupo y analizar su distribución.
  • Esto permite ver si coches más nuevos tienden a tener menos odómetro.
In [5]:
#Gráfico de dispersión
sample_data = car_data.copy()
sample_data['model_year_filled'] = sample_data['model_year'].fillna(0)

fig = px.scatter(
    sample_data,
    x='odometer',
    y='price',
    color= 'year_bucket',
    size='model_year_filled',
    opacity=0.7,
    hover_data=['model', 'model_year', 'fuel'],
    title='Precio vs Kilometraje',
    category_orders={'year_bucket': ['<1990','1990s','2000s','2010-14','2015-17','2018-19','2020-21','2022+']}
)
fig.update_layout(title_x=0.5)
fig.show()

Observaciones sobre Price vs Odometer¶

  • Se usa una muestra (2000 registros) para mantener la interactividad fluida.
  • El color ayuda a detectar si un tipo o rango de año tiene un patrón diferente.
  • El tamaño por model_year ayuda a ver la influencia del año (puntos más grandes = año mayor).
In [6]:
# Histograma de precios con booleana
fig = px.histogram(
    car_data.query("price <= price.quantile(0.99)"),  # recortamos el 1% superior para ver mejor la forma
    x='price',
    nbins=50,
    title='Distribución de precios (recortado al 99%)',
)
fig.update_layout(xaxis_title='Precio (USD)', yaxis_title='Cantidad', title_x=0.5)
fig.show()

Resumen¶

  • Limpiamos variables clave y creamos year_bucket.
  • Mostramos distribuciones (kilometraje y precio) y una relación (precio vs km) con muestreo para mantener interactividad.
  • Siguientes pasos: preparar métricas por modelo/plataforma, y pasar visualizaciones más limpias a Streamlit (app.py).